/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package UDPFiles;

import MNetSockets.DataChanel.DataClient;
import MNetSockets.DataChanel.IUDPDataReader;
import java.io.File;
import java.io.IOException;
import java.net.SocketAddress;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.sf.json.JSONObject;

/**
 * UDP文件传输类
 *
 * @author wcss
 */
public class UDPFileServer implements IUDPDataReader, Runnable {

    public HashMap<String, UDPFileReader> shareFiles = new HashMap<String, UDPFileReader>();
    public HashMap<String, UDPFileWriter> downFiles = new HashMap<String, UDPFileWriter>();
    public HashMap<String, Boolean> receiveStates = new HashMap<String, Boolean>();
    public Queue<String> needReceives = new LinkedList<String>();
    public ArrayList<SocketAddress> downAddress = new ArrayList<SocketAddress>();
    public DataClient client = null;
    public File bufferDir = null;
    public IUDPDataReader dataReceiveEvent;
    public int filesBufferSize = 8192;
    public String currentTaskFileCode = "";
    public IUDPFileStateEvent stateEvent;
    public long currentDownloadSize = 0;
    private JSONObject jsonTemplate = null;
    
    /**
     * 生成JSON模板
     */
    private void makeJSONTemplate()
    {
        jsonTemplate = JSONObject.fromObject(UDPDataCmd.makeControlCmd("aa", ""));
    }

    /**
     * 提交状态事件
     *
     * @param stateCode
     * @param text
     */
    public void handleChangeStateEvent(String stateCode, String text) {
        if (stateEvent != null) {
            stateEvent.changeState(this, stateCode, text);
        }
    }

    /**
     * 构造函数
     *
     * @param port
     * @param bufDir
     * @throws Exception
     */
    public UDPFileServer(int port, String bufDir) throws Exception 
    {
        makeJSONTemplate();
        this.bufferDir = new File(bufDir);
        this.bufferDir.mkdirs();

        client = new DataClient(port);
        client.receiveEvent = this;        
        client.start();

        Thread tt = new Thread(this);
        tt.setDaemon(true);
        tt.start();
    }

    /**
     * 添加共享
     *
     * @param shareFile
     * @throws Exception
     */
    public void addShare(File shareFile) throws Exception {
        if (shareFile.exists()) {
            UDPFileReader ufr = new UDPFileReader(shareFile);
            if (!shareFiles.containsKey(ufr.getFileCode())) {
                ufr.open();
                shareFiles.put(ufr.getFileCode(), ufr);
            }

        }
    }

    /**
     * 开始下载任务
     *
     * @param fileCode
     * @param destFile
     * @param remotes
     */
    public void StartDownTask(String fileCode, File destFile, SocketAddress[] remotes) throws Exception {
        if (downFiles.size() <= 0) {
            this.currentTaskFileCode = fileCode;
            File tempFile = new File(bufferDir.getAbsolutePath() + "/" + currentTaskFileCode + ".downTemp");

            if (tempFile.exists()) {
                tempFile.delete();
            }

            UDPFileWriter ufw = new UDPFileWriter(tempFile, destFile, this.filesBufferSize);
            downAddress.clear();
            for (SocketAddress sa : remotes) {
                downAddress.add(sa);
            }

            this.downFiles.put(this.currentTaskFileCode, ufw);
            currentDownloadSize = 0;
            receiveStates.clear();
            needReceives.clear();

        } else {
            throw new Exception("Task is Running");
        }
    }

    /**
     * 停止下载任务
     *
     * @param fileCode
     */
    public void stopDownTask() {
        this.downAddress.clear();
        this.needReceives.clear();
        try {
            this.downFiles.get(this.currentTaskFileCode).closeTemp();
        } catch (IOException ex) {
            Logger.getLogger(UDPFileServer.class.getName()).log(Level.SEVERE, null, ex);
        }
        this.downFiles.clear();
        this.currentTaskFileCode = "";
        this.receiveStates.clear();
    }

    /**
     * 发送消息根据地址列表序号
     *
     * @param addressIndex
     * @param udc
     * @throws Exception
     */
    public void sendUDPMsg(int addressIndex, UDPDataCmd udc) throws Exception {
        if (downAddress.size() > addressIndex) {
            this.fastSendWithJSON(downAddress.get(addressIndex), udc);
        }
    }

    /**
     * 发送消息给地址列表中所有个点
     *
     * @param udc
     * @throws Exception
     */
    public void sendUDPMsgToAll(UDPDataCmd udc) throws Exception {
        for (int k = 0; k < downAddress.size(); k++) {
            sendUDPMsg(k, udc);
        }
    }

    /**
     * 生成获取包数量消息
     *
     * @param fileCode
     * @return
     */
    public UDPDataCmd makeGetPackCountMsg(String fileCode) {
        return UDPDataCmd.makeControlCmd(UDPCmdFinal.getPackCount, fileCode);
    }

    /**
     * 生成设置包数量消息
     *
     * @param fileCode
     * @return
     */
    public UDPDataCmd makeSetPackCountMsg(String fileCode, long packcount) {
        return UDPDataCmd.makeControlCmd(UDPCmdFinal.setPackCount, fileCode, packcount);
    }

    /**
     * 生成远程错误消息
     *
     * @param fileCode
     * @return
     */
    public UDPDataCmd makeRemoteErrorMsg(String fileCode) {
        return UDPDataCmd.makeControlCmd(UDPCmdFinal.remoteError, fileCode);
    }

    /**
     * 生成获取包数据消息
     *
     * @param fileCode
     * @param dataIndex
     * @return
     */
    public UDPDataCmd makeRequestPackMsg(String fileCode, long dataIndex) {
        return UDPDataCmd.makeControlCmd(UDPCmdFinal.requestPack, fileCode, dataIndex);
    }

    /**
     * 生成存储包数据消息
     *
     * @param fileCode
     * @param dataIndex
     * @return
     */
    public UDPDataCmd makeReceivePackMsg(String fileCode, long dataIndex) throws Exception {
        if (this.shareFiles.containsKey(fileCode)) {

            return UDPDataCmd.makeFileCmdWithFile(UDPCmdFinal.receivePack, dataIndex, this.shareFiles.get(fileCode), (dataIndex - 1) * 500);
        } else {
            throw new Exception("Code(" + fileCode + ") is not share!");
        }
    }

    /**
     * JSON指令快速发送
     * @param isa
     * @param json 
     */
    public synchronized void fastSendWithJSON(SocketAddress isa,UDPDataCmd udc) throws Exception
    {
        if (isa != null && udc != null)
        {
            jsonTemplate.put("cmd", udc.getCmd());
            jsonTemplate.put("data", udc.getData());
            jsonTemplate.put("dataIndex", udc.getDataIndex());
            jsonTemplate.put("totalCode", udc.getTotalCode());
            jsonTemplate.put("subCode", udc.getSubCode());
            client.send(isa, jsonTemplate.toString());
            
        }else
        {
            throw new Exception("Send Error!");
        }
        
    }
    
    /**
     * 初始化接收状态
     *
     * @param packCount
     */
    public void initReceiveStates(long packCount) {
        this.needReceives.clear();
        this.receiveStates.clear();
        for (int k = 1; k <= packCount; k++) {
            this.receiveStates.put(k + "", false);
            this.needReceives.offer(k + "");
        }
    }

    @Override
    public void dataRead(Object o, SocketAddress sa, Object o1) {
        try {
            String sourceData = o1.toString().replace("{@@}", "").replace("{##}", "");
            JSONObject jobj = JSONObject.fromObject(sourceData);
            String cmd = jobj.getString("cmd");
            String total = jobj.getString("totalCode");
            String sub = jobj.getString("subCode");
            long dataIndex = jobj.getLong("dataIndex");
            String content = jobj.getString("data");

            if (cmd.equals(UDPCmdFinal.getPackCount)) {
                if (this.shareFiles.containsKey(total)) {
                    fastSendWithJSON(sa, this.makeSetPackCountMsg(total, this.shareFiles.get(total).getLength() / 500));
                } else {
                    fastSendWithJSON(sa, this.makeRemoteErrorMsg(total));
                }

            } else if (cmd.equals(UDPCmdFinal.setPackCount)) {
                if (this.receiveStates.size() <= 0) {
                    initReceiveStates(dataIndex);
                    try
                    {
                       downFiles.get(total).init(dataIndex * 500);
                    }catch(Exception ex)
                    {
                        this.handleChangeStateEvent(UDPCmdFinal.createTempFileErrorState, "创建临时文件失败！");
                    }
                }
            } else if (cmd.equals(UDPCmdFinal.requestPack)) {
                if (this.shareFiles.containsKey(total)) 
                {
                    this.handleChangeStateEvent(UDPCmdFinal.requestState, dataIndex + "");
                    fastSendWithJSON(sa, this.makeReceivePackMsg(total, dataIndex));
                } else {
                    fastSendWithJSON(sa, this.makeRemoteErrorMsg(total));
                }

            } else if (cmd.equals(UDPCmdFinal.receivePack)) {
                if (this.downFiles.containsKey(total)) {
                    if (this.receiveStates.get(dataIndex + "")) {
                        //已收到
                    } else 
                    {
                        byte[] dataBytes = Base64Tool.decodeFromBase64(content);
                        if (this.downFiles.get(total).isUseful(sub,dataBytes)) {
                            this.receiveStates.put(dataIndex + "", true);
                            currentDownloadSize += 500;
                            this.handleChangeStateEvent(UDPCmdFinal.progressState, currentDownloadSize + "");
                            this.downFiles.get(total).saveToBuffer(dataIndex,dataBytes);
                        } else {
                            this.needReceives.offer(dataIndex + "");
                        }
                    }
                }
            } else if (cmd.equals(UDPCmdFinal.remoteError)) {
                if (this.downAddress.size() <= 1) {
                    this.stopDownTask();
                    this.handleChangeStateEvent(UDPCmdFinal.errorState, "远端发生错误下载工作停止！");
                } else {
                    this.handleChangeStateEvent(UDPCmdFinal.noHaveState, "Peer(" + sa.toString() + ") 发生错误！");
                }
            }

            if (dataReceiveEvent != null) {
                dataReceiveEvent.dataRead(o, sa, o1);
            }

        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    @Override
    public void run() {
        while (true) {

            if (this.currentTaskFileCode != null && !this.currentTaskFileCode.isEmpty()) {
                if (this.receiveStates.size() <= 0) {
                    try {
                        //需要获取状态
                        this.sendUDPMsgToAll(this.makeGetPackCountMsg(this.currentTaskFileCode));
                    } catch (Exception ex) {
                        Logger.getLogger(UDPFileServer.class.getName()).log(Level.SEVERE, null, ex);
                    }

                } else {
                    //开始工作
                    if (this.needReceives.size() > 0) {
                        long lastIndex = Long.parseLong(this.needReceives.poll());
                        for (SocketAddress saa : this.downAddress) {
                            try {
                                fastSendWithJSON(saa, this.makeRequestPackMsg(this.currentTaskFileCode, lastIndex));
                            } catch (Exception ex) {
                                Logger.getLogger(UDPFileServer.class.getName()).log(Level.SEVERE, null, ex);
                            }

                            if (this.needReceives.size() > 0) {
                                lastIndex = Long.parseLong(this.needReceives.poll());
                            }

//                            try {
//                                Thread.sleep(10);
//                            } catch (InterruptedException ex) {
//                                Logger.getLogger(UDPFileServer.class.getName()).log(Level.SEVERE, null, ex);
//                            }
                        }
                    } else {
                        //已完成
                        Iterator iter = this.receiveStates.entrySet().iterator();
                        while (iter.hasNext()) {
                            Map.Entry entry = (Map.Entry) iter.next();
                            String key = entry.getKey().toString();
                            Boolean val = Boolean.parseBoolean(entry.getValue().toString());
                            if (!val)
                            {
                                if (!this.needReceives.contains(key))
                                {
                                    this.needReceives.offer(key);
                                }
                            }
                        }
                        
                        if (this.needReceives.size() <= 0)
                        {
                            try {
                                this.downFiles.get(this.currentTaskFileCode).makeDestFile(this.receiveStates.size());
                            } catch (Exception ex) {
                                Logger.getLogger(UDPFileServer.class.getName()).log(Level.SEVERE, null, ex);
                            }
                            this.stopDownTask();                            
                            this.handleChangeStateEvent(UDPCmdFinal.finishState, "下载完成");
                        }
                    }
                }
            }

            try {
                Thread.sleep(50);
            } catch (InterruptedException ex) {
                Logger.getLogger(UDPFileServer.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
}
